load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")

versions = struct(
    RULES_KOTLIN = struct(
        urls = [
            "https://github.com/bazelbuild/rules_kotlin/archive/v1.5.0-beta-3.zip",
        ],
        sha256 = "d8650bb939d87a080448ffbbbd1594f5ae054738df5d9471941c18784aa139b9",
        prefix = "rules_kotlin-1.5.0-beta-3",
    ),
    RULES_KOTLIN_HEAD = struct(
        urls = [
            "https://github.com/bazelbuild/rules_kotlin/archive/refs/heads/master.zip",
        ],
        sha256 = "cd5388f019d856ede82dd2a3dc7cf935d4925340fee56094f1e23d93a62b5439",
        prefix = "rules_kotlin-master",
    ),
)

def archive_repository_implementation(repository_ctx):
    attr = repository_ctx.attr

    # In a perfect world, the local archive path would be set via a build config setting
    # Alas, a repository label is never replaced with a target -- meaning repository_rules are
    # evaluated first (reasonable) but disallow using settings as they need to be targets.
    # One day, one hopes that there will be a flag to control repository behavior.
    # Instead, environment variables are a tried and true workaround.
    environ = repository_ctx.os.environ

    if attr.env_archive in environ and environ[attr.env_archive]:
        repository_ctx.report_progress("Using release archive from %s" % environ[attr.env_archive])
        repository_ctx.extract(
            archive = environ[attr.env_archive],
            stripPrefix = environ.get(attr.env_archive_prefix, ""),
        )
    elif attr.env_stable in environ:
        repository_ctx.report_progress("Using release archive from %s" % attr._remote_urls)
        repository_ctx.download_and_extract(
            attr._remote_urls,
            sha256 = attr._remote_sha256,
            stripPrefix = attr._remote_prefix,
        )
    else:
        print("build %s" % repository_ctx.execute(["date"]).stdout)

        # not OS safe. Presuming linux-likes until complaints.
        release_archive = attr.local_release_archive_target

        # move to execroot, then reference local repo.
        workspace = repository_ctx.path("../../%s" % release_archive.workspace_root)

        if not workspace.exists:
            fail("local workspace %s does not exist" % workspace)

        target = "//%s:%s" % (release_archive.package, release_archive.name)

        workspace_file = workspace.get_child("WORKSPACE")

        # if kt_download_local_dev_dependencies isn't there, the WORKSPACE file is invalid
        # probably generated by http_archive.
        if not ("kt_download_local_dev_dependencies" in repository_ctx.read(workspace_file)):
            repository_ctx.report_progress(
                "Symlinking WORKSPACE to WORKSPACE.dev.bazel in %s" % (workspace.basename),
            )

            # Remove existing WORKSPACE, as it might be created by http_archive
            repository_ctx.delete(workspace_file)

            # Symlink the WORKSPACE
            repository_ctx.symlink(
                workspace.get_child("WORKSPACE.dev.bazel"),
                workspace_file,
            )
        repository_ctx.report_progress(
            "Building %s in %s... (may take a while.)" % (target, workspace.basename),
        )

        bazel = repository_ctx.which("bazel") or repository_ctx.which("bazelisk")
        if not bazel:
            fail("bazel or bazelisk not found in PATH. Good trick.")
        result = repository_ctx.execute(
            [
                bazel,
                "build",
                target,
            ],
            environment = {
                "USE_BAZEL_VERSION": attr.bazel_version,
            },
            working_directory = str(workspace),
            timeout = 1200,  # builds can take a minute. Or ten.
        )

        if result.return_code:
            fail("%s: %s\n%s\n\nenv:%s" % (str(workspace), result.stderr, result.stdout, environ))
        release_artifact = workspace.get_child("bazel-bin")
        if release_archive.package:
            release_artifact = release_artifact.get_child(release_archive.package)
        release_artifact = release_artifact.get_child(release_archive.name + ".tgz")

        repository_ctx.extract(
            archive = release_artifact,
        )

        # Update the timestamp on the repository rule source to ensure it's always re-evaluated.
        # This appears to cause issues on windows with permissions -- so don't do it.
        if not "win" in repository_ctx.os.name:
            self = workspace
            for segment in "src/main/starlark/release_archive/repository.bzl".split("/"):
                self = self.get_child(segment)
            repository_ctx.execute([
                "touch",
                str(self),
            ])

# not windows compatible.
# buildifier: disable=unused-variable
def _find_workspace(attr, environ, path):
    if attr.local_repository_path_env in environ:
        workspace = path(environ[attr.local_repository_path_env])
        if workspace.exists:
            return workspace
    if attr.local_repository_path.startswith("/"):
        return path(attr.local_repository_path)

    return path(environ["PWD"] + "/" + attr.local_repository_path)

_archive_repository = repository_rule(
    implementation = archive_repository_implementation,
    local = True,
    configure = True,
    attrs = {
        "bazel_version": attr.string(
            doc = "Bazel version to use for building the release archive",
            default = "7.0.0",
        ),
        "_remote_urls": attr.string_list(
            doc = "A list of urls for the archive",
            default = versions.RULES_KOTLIN.urls,
        ),
        "_remote_sha256": attr.string(
            doc = "sha256 of the archive",
            default = versions.RULES_KOTLIN.sha256,
        ),
        "_remote_prefix": attr.string(
            doc = "prefix to remove from the remote archive",
            default = versions.RULES_KOTLIN.prefix,
        ),
        "local_release_archive_target": attr.label(
            doc = "release_archive rule.",
        ),
        "env_archive": attr.string(
            doc = "release archive path environment variable name",
            default = "RULES_KOTLIN_ARCHIVE",
        ),
        "env_archive_prefix": attr.string(
            doc = "release archive path environment variable name",
            default = "RULES_KOTLIN_ARCHIVE_PREFIX",
        ),
        "env_stable": attr.string(
            doc = "Use stable release",
            default = "RULES_KOTLIN_STABLE",
        ),
    },
)

def archive_repository(
        name,
        local_path = "../..",
        release_archive_target = Label("//:rules_kotlin_release"),
        remote_source_archive = None,
        source_repository_name = None):
    """
    archive_repository builds rules_kotlin from either a local_path or a remote archive.

    Args:
        name: of the repository
        local_path: to an un-archived rules_kotlin repository. Defaults to '../..'.
        release_archive_target: Label to build a rules_kotlin release
        remote_source_archive: struct(
            urls = list of urls for a remote source archive,
            sha256 = hash of remote archive,
            prefix = directory string to strip from extracted archive
        )
        source_repository_name: of the release archive. Defaults to `name`_head. If set to an existing
          repository no new repository will be created.
    """
    source_repository_name = source_repository_name or ("%s_head" % name)
    if remote_source_archive:
        maybe(
            http_archive,
            name = source_repository_name,
            sha256 = remote_source_archive.sha256,
            strip_prefix = remote_source_archive.prefix,
            urls = remote_source_archive.urls,
        )
    else:
        maybe(
            native.local_repository,
            name = source_repository_name,
            path = local_path,
        )
    _archive_repository(
        name = name,
        local_release_archive_target = "@%s//%s:%s" % (
            source_repository_name,
            release_archive_target.package,
            release_archive_target.name,
        ),
    )
